package scheduler

import (
	"context"
	"github.com/zeromicro/go-zero/core/logx"
	"sort"
	"sync"
	"time"
)

// Cron cron表达式
// 参考 https://github.com/robfig/cron/blob/master/cron.go 改造
type Cron struct {
	entries       []*Task
	chain         *Chain
	stop          chan struct{}
	add           chan *Task
	remove        chan TaskID
	snapshot      chan chan []Task
	running       bool
	logger        logx.Logger
	runningLocker sync.Mutex
	location      *time.Location
	parser        IScheduleParse
	nextID        TaskID
	jobWaiter     sync.WaitGroup
}

// NewCron 创建一个cron
func NewCron(ctx context.Context, opts ...Option) *Cron {
	c := &Cron{
		chain:         NewChain(),
		stop:          make(chan struct{}),
		add:           make(chan *Task),
		remove:        make(chan TaskID),
		snapshot:      make(chan chan []Task),
		logger:        logx.WithContext(ctx),
		nextID:        0,
		jobWaiter:     sync.WaitGroup{},
		entries:       nil,
		parser:        standardParser,
		running:       false,
		runningLocker: sync.Mutex{},
		location:      time.Local,
	}
	for _, opt := range opts {
		opt(c)
	}
	return c
}

// AddFunc 添加一个函数
func (c *Cron) AddFunc(expression string, cmd func()) (TaskID, error) {
	return c.AddJob(expression, FuncJob(cmd))
}

// AddJob 添加一个任务
func (c *Cron) AddJob(expression string, job IJob) (TaskID, error) {
	schedule, err := c.parser.Parse(expression)
	if err != nil {
		return 0, err
	}
	return c.Schedule(schedule, job), nil
}

// Schedule 添加一个任务
func (c *Cron) Schedule(schedule ISchedule, job IJob) TaskID {
	c.runningLocker.Lock()
	defer c.runningLocker.Unlock()
	c.nextID++
	entry := &Task{
		ID:         c.nextID,
		Schedule:   schedule,
		WrapperJob: c.chain.Then(job),
		Job:        job,
	}
	if !c.running {
		c.entries = append(c.entries, entry)
	} else {
		c.add <- entry
	}
	return entry.ID
}

// TaskList 获取所有的任务
func (c *Cron) TaskList() []Task {
	c.runningLocker.Lock()
	defer c.runningLocker.Unlock()
	if c.running {
		replyChan := make(chan []Task, 1)
		c.snapshot <- replyChan
		return <-replyChan
	}
	return c.entrySnapshot()
}

// Location 获取时间区域
func (c *Cron) Location() *time.Location {
	return c.location
}

// FindEntryById 查找任务
func (c *Cron) FindEntryById(id TaskID) Task {
	for _, entry := range c.TaskList() {
		if entry.ID == id {
			return entry
		}
	}
	return Task{}
}

// RemoveById 移除任务
func (c *Cron) RemoveById(id TaskID) {
	c.runningLocker.Lock()
	defer c.runningLocker.Unlock()
	if c.running {
		c.remove <- id
	} else {
		c.removeEntry(id)
	}
}

// Stop 停止任务
func (c *Cron) Stop(ctx context.Context) context.Context {
	c.runningLocker.Lock()
	defer c.runningLocker.Unlock()
	if c.running {
		c.stop <- struct{}{}
		c.running = false
	}
	ct, cancel := context.WithCancel(ctx)
	//defer cancel()
	go func() {
		c.jobWaiter.Wait()
		defer cancel()
	}()
	return ct
}

// Start 启动任务
func (c *Cron) Start() {
	c.runningLocker.Lock()
	defer c.runningLocker.Unlock()
	if c.running {
		return
	}
	c.running = true
	go c.run()
}

func (c *Cron) Run() {
	c.runningLocker.Lock()
	defer c.runningLocker.Unlock()
	if c.running {
		return
	}
	c.running = true
	c.run()
}

/**
*  ### 私有方法 ###
 */

// startJob 启动任务
func (c *Cron) startJob(job IJob) {
	c.jobWaiter.Add(1)
	go func() {
		defer c.jobWaiter.Done()
		job.Run()
	}()
}

func (c *Cron) entrySnapshot() []Task {
	var entries = make([]Task, len(c.entries))
	for i, entry := range c.entries {
		entries[i] = *entry
	}
	return entries
}

// removeEntry 移除任务
func (c *Cron) removeEntry(id TaskID) {
	var entries []*Task
	for i, e := range c.entries {
		if e.ID != id {
			entries = append(entries, c.entries[i])
		}
	}
	c.entries = entries
}

// now 获取当前时间
func (c *Cron) now() time.Time {
	return time.Now().In(c.location)
}

// run 运行任务
func (c *Cron) run() {
	c.logger.Info("start")
	now := c.now()
	for _, entry := range c.entries {
		entry.Next = entry.Schedule.Next(now)
		c.logger.Info("schedule", "now", now, "entry", entry.ID, "next", entry.Next)
	}

	for {
		// 按照时间排序
		sort.Sort(TaskByTime(c.entries))

		var timer *time.Timer
		if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
			timer = time.NewTimer(100000 * time.Hour)
		} else {
			timer = time.NewTimer(c.entries[0].Next.Sub(now))
		}

		for {
			select {
			case now = <-timer.C:
				now = now.In(c.location)
				c.logger.Info("wake", "now", now)
				for _, e := range c.entries {
					if e.Next.After(now) || e.Next.IsZero() {
						break
					}
					c.startJob(e.WrapperJob)
					e.Prev = e.Next
					e.Next = e.Schedule.Next(now)
					c.logger.Info("schedule", "now", now, "entry", e.ID, "next", e.Next)
				}
			case newEntry := <-c.add:
				timer.Stop()
				now = c.now()
				newEntry.Next = newEntry.Schedule.Next(now)
				c.entries = append(c.entries, newEntry)
				c.logger.Info("add", "now", now, "entry", newEntry.ID, "next", newEntry.Next)
			case replyChan := <-c.snapshot:
				replyChan <- c.entrySnapshot()
				continue
			case <-c.stop:
				timer.Stop()
				c.logger.Info("stop")
				return
			case id := <-c.remove:
				timer.Stop()
				now = c.now()
				c.removeEntry(id)
				c.logger.Info("remove", "now", now, "id", id)

			}
			break
		}
	}

}
